#include "stdafx.h"
#include "core/engine.h"

namespace natrium {
	namespace core {
		Engine::Engine():
			mConfig(new Config())
		{
			//although strictly speaking GLFW is not 'core' material, it spans multiple components - 
			//video and input, and as such should be initialized at a higher level; this is one option 
			//for such. Note that the input system still cannot initialize properly without a fully
			//initialized window; its initialization should make use of the SystemInitialized event.
			glfwInit(); 

			add(std::make_unique<video::Video>());
			add(std::make_unique<input::Input>());
		}

		Engine::~Engine() {
			glfwTerminate();
		}

		bool Engine::add(std::unique_ptr<System>&& s) {
			if (mSystemMap.find(s->getName()) == mSystemMap.end()) {
				mSystemMap.insert(std::make_pair(s->getName(), std::move(s)));

				return true;
			}
			else {
				gLog << "Duplicate system: " << s->getName();
				return false;
			}
		}

		bool Engine::remove(System* s) {
			assert(s != nullptr);

			auto it = mSystemMap.find(s->getName());
		
			if (it == mSystemMap.end()) {
				gLog << "Unable to remove system, not found: " << s->getName();
				return false;
			}

			mSystemMap.erase(it);

			return true;
		}

		System* Engine::get(const std::string& name) const {
			const auto it = mSystemMap.find(name);

			if (it == mSystemMap.end()) {
				gLog << "Cannot find system: " << name;
				throw std::runtime_error("Cannot find system");
			}

			return it->second.get();
		}

		void Engine::run() {
			//consolidate all settings for registered systems
			for (const auto& elem: mSystemMap)
				mConfig->settings().add(elem.second->mSettings);
		
			//the application is in a class of its own ;)
			if (mApplication.get())
				mConfig->settings().add(mApplication->mSettings);

			gLog << "Loading OA.cfg...";
			mConfig->load("OA.cfg");
			gLog << "done.";

			initializeSystems();
			mKernel.start();
			shutdownSystems();
		}

		void Engine::stop() {
			mChan.broadcast(Kernel::StopEvent());
		}

		void Engine::setApplication(std::unique_ptr<app::Application>&& app) {
			mApplication = std::move(app);
			mApplication->mEngine = this;
		}

		const app::Application* Engine::application() {
			return mApplication.get();
		}

		EventChannel& Engine::events() {
			return mChan;
		}

		void Engine::initializeSystems() {
			for (auto& it: mSystemMap) {
				gLog.info() << "Initializing " << it.second->getName();
				it.second->init();

				if (it.second->mUpdater.get())
					mKernel.add(it.second->mUpdater);
			}

			if (mApplication.get()) {
				gLog.info() << "Initializing application: " << mApplication->getName();
				mApplication->init();

				if (mApplication->mUpdater.get())
					mKernel.add(mApplication->mUpdater);
			}
		}

		void Engine::shutdownSystems() {
			if (mApplication.get()) {
				gLog.info() << "Shutting down app: " << mApplication->getName();
				mApplication->shutdown();
				mApplication.reset();
			}

			for (auto& it: mSystemMap) {
				gLog.info() << "Shutting down " << it.second->getName();
				it.second->shutdown();
			}
		}
	}
}